package base.thread_JUC.demo01;

/**
 * 顺序一致性理论内存模型
 * 顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型，它为程序员提供了极强的内存可见性保证。顺序一致性内存模型有两大特性：
 * 一个线程中的所有操作必须按照程序的顺序来执行。
 * （不管程序是否同步）所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中，每个操作都必须原子执行且立刻对所有线程可见。
 *
 * 实际JMM模型
 * 顺序一致性模型只是一个理想化了的模型，在实际的JMM实现中，为了尽量提高程序运行效率，和理想的顺序一致性内存模型有以下差异：
 * 在顺序一致性模型中，所有操作完全按程序的顺序串行执行。在JMM中不保证单线程操作会按程序顺序执行（即指令重排序）。
 * 顺序一致性模型保证所有线程只能看到一致的操作执行顺序，而JMM不保证所有线程能看到一致的操作执行顺序。
 * 顺序一致性模型保证对所有的内存写操作都具有原子性，而JMM不保证对64位的long型和double型变量的读/写操作具有原子性（分为2个32位写操作进行，本文无关不细阐述）
 *
 * 指令重排序
 * 指令重排序是指编译器或处理器为了优化性能而采取的一种手段，在不存在数据依赖性情况下（如写后读，读后写，写后写），调整代码执行顺序。 举个例子:
 * //A
 * double pi  = 3.14;
 * //B
 * double r   = 1.0;
 *  //C
 * double area = pi * r * r;
 *
 * 这段代码C依赖于A,B，但A，B没有依赖关系，所以代码可能有2种执行顺序：
 * A->B->C
 * B->A->C 但无论哪种最终结果都一致，这种满足单线程内无论如何重排序不改变最终结果的语义，被称作as-if-serial语义，
 * 遵守as-if-serial语义的编译器，runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉： 单线程程序是按程序的顺序来执行的。
 *
 * 双重检查锁问题解决方案
 * 回来看下我们出问题的双重检查锁程序，它是满足as-if-serial语义的吗？是的，单线程下它没有任何问题，但是在多线程下，会因为重排序出现问题。
 * 解决方案就是大名鼎鼎的volatile关键字，对于volatile我们最深的印象是它保证了”可见性“，它的”可见性“是通过它的内存语义实现的:
 * 写volatile修饰的变量时，JMM会把本地内存中值刷新到主内存
 * 读volatile修饰的变量时，JMM会设置本地内存无效
 *
 * 重点：为了实现可见性内存语义，编译器在生成字节码时，会在指令序列中插入内存屏障来防止重排序！
 *
 */
public class Client5 {
    /**
     * volatile 轻量级同步机制：
     * 1、保证可见性
     * 2、不保证原子性
     * 3、禁止指令重排
     */
    // volatile 禁止指令重排
    private static volatile Client5 instance;

    private Client5(){
        System.out.println(Thread.currentThread().getName()+" 我是构造方法 Client1()");
    }

    /**
     * 存在指令重排。instance = new Client5();
     * 在java中创建一个对象并非是一个原子操作，可以被分解成三行伪代码：
     * //1：分配对象的内存空间
     * memory = allocate();
     * //2：初始化对象（属性赋初始值）
     * ctorInstance(memory);
     * //3：设置instance指向刚分配的内存地址
     * instance = memory;
     *
     * 上面三行伪代码中的2和3之间，可能会被重排序（在一些JIT编译器中）,即编译器或处理器为提高性能改变代码执行顺序，
     * 这一部分的内容稍后会详细解释，重排序之后的伪代码是这样的：
     * //1：分配对象的内存空间
     * memory = allocate();
     * //3：设置instance指向刚分配的内存地址
     * instance = memory;
     * //2：初始化对象
     * ctorInstance(memory);
     *
     * 在单线程程序下，重排序不会对最终结果产生影响，但是并发的情况下，可能会导致某些线程访问到未初始化的变量。本案例很难见到该现象，但本案例的确
     * 存在在多线程情况下会遇到这种情况。
     */
    public static Client5 getInstance(){
        if (instance == null) {
            synchronized (Client5.class){
                if (instance == null) {
                    instance = new Client5();
                }
            }
        }
        return instance;
    }


    public static void main(String[] args) {
        for (int i = 0; i < 2000; i++) {
            new Thread(()->{
                Client5.getInstance();
            },String.valueOf(i)).start();
        }
    }
}
